<!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
<template>
	<!-- 查询表单 card -->
	<template v-if="!custSearch">
		<SearchForm
			:search="searchEstend"
			:reset="resetEstend"
			:searchParam="searchParam"
			:columns="searchColumns"
			:searchCol="searchCol"
			v-show="isShowSearch"
			@change-collapsed="(val:boolean) => hideMoreSearch = val"
		>
			<template v-for="(col, i) in searchColumns" :key="col.prop">
				<template v-if="col.search?.slot">
					<GridItem :style="{ display: i >= Number(searchCol) && hideMoreSearch ? 'none' : 'block' }">
						<el-form-item :label="`${col.label} :`">
							<slot :name="`search-${col.prop}`" :column="col"></slot>
						</el-form-item>
					</GridItem>
				</template>
			</template>
		</SearchForm>
	</template>
	<!-- 表格内容 card -->
	<div class="card table pt-4" :class="`${props.class} ${isGrid ? '' : 'bg-white pl-4 pr-4'}`">
		<!-- 表格头部 操作按钮 -->
		<div v-if="showHeader" class="table-header">
			<div class="header-button-lf">
				<div class="text-base font-bold" v-if="title"><span class="title-left-line"></span>{{ title }}</div>
				<slot name="tableHeader" :selectedListIds="selectedListIds" :selectList="selectedList" :isSelected="isSelected"></slot>
			</div>
			<div class="header-button-ri" v-if="toolButton">
				<el-button class="iconfont iconrefresh-ccw" :title="$t('protable.btn_title.refresh')" circle @click="getTableList">
				</el-button>
				<el-button
					circle
					:title="isGrid ? $t('protable.btn_title.list') : $t('protable.btn_title.card')"
					v-if="!noList && toolbar.includes('grid') && columns.length"
					@click="isGrid = !isGrid"
				>
					<i :class="`iconfont ${isGrid ? 'iconlist' : 'iconbx-customize'}`"></i>
				</el-button>

				<template v-if="columns.length && !noList">
					<el-popover placement="bottom-start" :width="400" trigger="click">
						<template #reference>
							<el-button class="iconfont iconmenu1" circle :title="$t('protable.btn_title.columns')"> </el-button>
						</template>
						<!-- 列设置 -->
						<ColSetting ref="colRef" v-model:colSetting="colSetting" />
					</el-popover>
				</template>
				<el-button
					class="iconfont iconsearch"
					:title="isShowSearch ? $t('protable.btn_title.show') : $t('protable.btn_title.hide')"
					circle
					v-if="searchColumns.length"
					@click="changeShowSearch"
				>
				</el-button>
			</div>
			<div v-else class="header-button-ri">
				<slot name="headerRightExt" />
			</div>
		</div>
		<!-- 卡片布局 -->
		<el-row v-if="isGrid" :gutter="gridGutter" style="overflow: scroll" class="h-full">
			<el-col
				v-for="(item, index) in tableData"
				:key="index"
				:span="gridSpan"
				class="mb-4"
				:class="!hideCardCheck && (selectedListIds.includes(item[selectId]) ? 'card-item-active' : '')"
				@click="
					() => {
						cardSelectionChange(item, true);
						emit('selected', selectedList);
					}
				"
			>
				<el-card body-style="padding: 0px" shadow="hover">
					<div
						class="checked-box"
						@click.stop="
							() => {
								cardSelectionChange(item, true);
								emit('selected', selectedList);
							}
						"
					/>
					<slot name="cardBody" :row="item" :index="index"></slot>
				</el-card>
			</el-col>
			<template v-if="!tableData.length">
				<slot name="empty">
					<Empty />
				</slot>
			</template>
		</el-row>
		<!-- 表格主体 -->
		<el-table
			v-else
			ref="tableRef"
			:stripe="stripe"
			:highlight-current-row="highlightRow"
			:default-expand-all="expandAll"
			:expand-row-keys="defaultExpandKeys"
			:tooltip-options="{ showAfter: 800, placement: 'bottom-start' }"
			v-bind="$attrs"
			:data="tableData"
			:border="border"
			:row-key="getRowKeys"
			:lazy="lazy"
			:load="loadChild"
			@selection-change="(rowArr:any[])=>{selectionChange(rowArr);emit('selected',selectedList)}"
			@row-click="rowClick"
			@expand-change="row => emit('rowExpand', row)"
			@sort-change="e => emit('sortChange', e)"
			:span-method="arraySpanMethod"
			:tree-props="{ children: 'children', hasChildren: lazy ? 'hasChildren' : 'none' }"
		>
			<!-- 默认插槽 -->
			<slot></slot>
			<template v-for="item in tableColumns" :key="item">
				<!-- selection || index -->
				<el-table-column
					v-bind="item"
					v-if="item.type == 'selection' || item.type == 'index'"
					:align="item.align || 'center'"
					:reserve-selection="item.type == 'selection'"
				>
				</el-table-column>
				<!-- expand 支持 tsx 语法 && 作用域插槽 (tsx > slot) -->
				<el-table-column v-if="item.type == 'expand'" v-bind="item" :align="item.align || 'left'" v-slot="scope">
					<component :is="item.render" :row="scope.row" v-if="item.render"> </component>
					<slot :name="item.type" :row="scope.row" :scope="scope" v-else></slot>
				</el-table-column>

				<!-- other 循环递归 -->
				<TableColumn v-if="!item.type && item.prop && item.isShow" :column="item">
					<template v-for="(slot, i) in Object.keys($slots)" :key="i" #[slot]="scope">
						<slot :name="slot" :row="scope.row" :rowIndex="scope.$index"></slot>
					</template>
				</TableColumn>
			</template>
			<!-- 插入表格最后一行之后的插槽 -->
			<template #append>
				<slot name="append"> </slot>
			</template>
			<!-- 表格无数据情况 -->
			<template #empty>
				<div class="table-empty">
					<slot name="empty">
						<!-- <img src="@/assets/images/notData.png" alt="notData" />
						<div>暂无数据</div> -->
						<div class="pro-table-empty" style="padding: 60px 0; line-height: 30px">
							<Empty :show-button="false" />
						</div>
					</slot>
				</div>
			</template>
		</el-table>
		<!-- 分页组件 -->
		<slot name="pagination">
			<Pagination
				v-if="pagination"
				:style="`${isGrid ? 'margin-top:-8px' : 'padding-bottom:8px'}`"
				:pageable="pageable"
				:layout="layout"
				:handleSizeChange="handleSizeChange"
				:handleCurrentChange="handleCurrentChange"
			/>
		</slot>
	</div>
</template>
<script setup lang="ts" name="ProTable">
import Empty from "@/components/Empty";
import GridItem from "@/components/Grid/components/GridItem.vue";
import { BreakPoint } from "@/components/Grid/interface";
import { ColumnProps } from "@/components/ProTable/interface";
import SearchForm from "@/components/SearchForm/index.vue";
import { useSelection } from "@/hooks/useSelection";
import { useTable } from "@/hooks/useTable";
import { handleProp } from "@/utils/util";
import { ElTable, TableProps } from "element-plus";
import { provide, ref } from "vue";
import ColSetting from "./components/ColSetting.vue";
import Pagination from "./components/Pagination.vue";
import TableColumn from "./components/TableColumn.vue";
const emit = defineEmits(["searchCollapsed", "rowExpand", "clickRow", "reset", "search", "sortChange", "selected"]);

const hideMoreSearch = ref(true);

interface ProTableProps extends Partial<Omit<TableProps<any>, "data">> {
	class?: string;
	columns: ColumnProps[]; // 列配置项
	toolbar?: string[];
	requestApi: (params: any) => Promise<any> | any; // 请求表格数据的api ==> 必传
	dataCallback?: (data: any) => any; // 返回数据的回调函数，可以对数据进行处理 ==> 非必传
	title?: string; // 表格标题，目前只在打印的时候用到 ==> 非必传
	pagination?: boolean; // 是否需要分页组件 ==> 非必传（默认为true）
	expandAll?: boolean; // 是否展开树列表 ==> 非必传（默认为true）
	initParam?: any; // 初始化请求参数 ==> 非必传（默认为{}）
	border?: boolean; // 是否带有纵向边框 ==> 非必传（默认为true）
	toolButton?: boolean; // 是否显示表格功能按钮 ==> 非必传（默认为true）
	selectId?: string; // 当表格数据多选时，所指定的 id ==> 非必传（默认为 id）
	searchCol?: number | Record<BreakPoint, number>; // 表格搜索项每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
	gridSpan?: number | undefined; //卡片栅格数量
	gridGutter?: number | undefined; //卡片栅格间距大小
	showSearch?: boolean; //卡片栅格间距大小
	custSearch?: boolean; //卡片栅格间距大小
	viewType?: string; //默认视图模式：list=表格,grid=卡片
	showHeader?: boolean; //是否显示头
	lazy?: boolean; //树形异步加载
	loadChild?: any; //加载子节点
	selectDefault?: any; //默认选中
	isClearSelection?: boolean; //勾选行是否去除其他勾选
	noList?: boolean; //不显示列表（纯卡片列表）
	spanShow?: boolean; //是否合并行列
	stripe?: boolean; // 斑马纹表格
	highlightRow?: boolean; // 是否高亮点击行
	hideCardCheck?: boolean; //是否隐藏卡片模式下选中
	layout?: any; //分页样式"total, sizes, prev, pager, next, jumper"
	ignoreInit?: boolean; //忽略表格初始查询
}
// 接受父组件参数，配置默认值
const props = withDefaults(defineProps<ProTableProps>(), {
	class: "",
	toolbar: () => [], //'grid','list','print'
	columns: () => [],
	expandAll: false,
	pagination: true,
	initParam: {},
	border: false,
	toolButton: true,
	selectId: "id",
	searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
	gridSpan: 8,
	gridGutter: 30,
	showSearch: true,
	custSearch: false,
	viewType: "list",
	showHeader: true,
	selectDefault: () => [], //默认选中
	isClearSelection: false, //勾选行是否去除其他勾选
	noList: false, //不显示列表（纯卡片列表）
	spanShow: false, // 是否合并行列
	stripe: true, // 斑马纹表格
	highlightRow: true, // 是否高亮点击行
	hideCardCheck: false, //是否隐藏卡片模式下选中
	layout: "total, sizes, prev, pager, next, jumper",
	ignoreInit: false
});

// 是否显示搜索模块
const isShowSearch = ref(props.showSearch);
// 是否显示搜索模块
const isGrid = ref(props.viewType == "grid");

// 表格 DOM 元素
const tableRef = ref<InstanceType<typeof ElTable>>();

// 表格多选 Hooks
const { selectionChange, getRowKeys, selectedList, selectedListIds, isSelected, cardSelectionChange } = useSelection(
	props.selectId
);
// 表格操作 Hooks
const {
	tableData,
	defaultExpandKeys,
	pageable,
	searchParam,
	searchInitParam,
	getTableList,
	search,
	reset,
	handleSizeChange,
	handleCurrentChange
} = useTable(props.requestApi, props.selectId, props.initParam, props.pagination, props.ignoreInit, props.dataCallback);

// 清空选中数据列表
const clearSelection = () => tableRef.value?.clearSelection() ?? selectedList.value.splice(0);
const changeShowSearch = () => {
	isShowSearch.value = !isShowSearch.value;
	emit("searchCollapsed", isShowSearch.value);
};
// 监听页面 initParam 改化，重新获取表格数据
watch(() => props.initParam, getTableList, { deep: true });

watch(
	() => tableData,
	() => {
		if (props.selectDefault.length) {
			props.selectDefault.map((item: any) => {
				tableRef.value!.toggleRowSelection({ [props.selectId]: item }, true);
			});
		}
		if (selectedListIds.value && selectedListIds.value.length) {
			// 自定义表格选中事件 解决表格选中分页的问题
			(selectedListIds.value as any).map((item: any) => {
				if (tableRef.value) {
					tableRef.value!.toggleRowSelection({ [props.selectId]: item }, true);
				}
			});
		}
	},
	{ deep: true }
);

// 接收 columns 并设置为响应式
const tableColumns = ref<ColumnProps[]>(props.columns);

// 定义 enumMap 存储 enum 值（避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择）
const enumMap = ref(new Map<string, { [key: string]: any }[]>());
provide("enumMap", enumMap);
const setEnumMap = async (col: ColumnProps) => {
	if (!col.enum) return;
	// 如果当前 enum 为后台数据需要请求数据，则调用该请求接口，并存储到 enumMap
	if (typeof col.enum !== "function") return enumMap.value.set(col.prop!, col.enum!);
	const { data } = await col.enum();
	enumMap.value.set(col.prop!, data);
};

// 扁平化 columns
const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => {
	columns.forEach(async col => {
		if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children));
		flatArr.push(col);

		// 给每一项 column 添加 isShow && isFilterEnum 默认属性
		col.isShow = col.isShow ?? true;
		col.isFilterEnum = col.isFilterEnum ?? true;

		// 设置 enumMap
		setEnumMap(col);
	});
	return flatArr.filter(item => !item._children?.length);
};

// flatColumns
const flatColumns = ref<ColumnProps[]>();
flatColumns.value = flatColumnsFunc(tableColumns.value);

// 过滤需要搜索的配置项 && 处理搜索排序
const searchColumns = flatColumns.value
	.filter(item => item.search)
	.sort((a, b) => (b.search?.order ?? 0) - (a.search?.order ?? 0));

// 设置搜索表单的默认值
searchColumns.forEach(column => {
	if (column.search?.defaultValue !== undefined && column.search?.defaultValue !== null) {
		searchInitParam.value[column.search.key ?? handleProp(column.prop!)] = column.search?.defaultValue;
	}
});
const rowClick = (row: any) => {
	if (props.isClearSelection) {
		let oldSelected = selectedListIds.value.includes(row[props.selectId]);
		//点击切换选中
		tableRef.value!.toggleRowSelection(row, !oldSelected);
	} else {
		clearSelection();
		tableRef.value!.toggleRowSelection(row, true);
	}
	emit("clickRow", row);
};
const toggleRowExpansion = (row: any, expanded?: boolean) => {
	tableRef.value!.toggleRowExpansion(row, expanded);
};

// 列设置 ==> 过滤掉不需要设置显隐的列
const colRef = ref();
const colSetting = tableColumns.value!.filter(item => {
	return item.type !== "selection" && item.type !== "index" && item.type !== "expand" && item.prop !== "operation";
});

const arraySpanMethod = (row: any) => {
	if (props.spanShow) {
		if (row.columnIndex == 0) {
			return row.row.rowSpan ? [row.row.rowSpan, 1] : [row.row.rowSpan, 0];
		} else if (row.columnIndex == 1) {
			return row.row.rowSpan1 ? [row.row.rowSpan1, 1] : [row.row.rowSpan1, 0];
		}
	} else {
		return [1, 1];
	}
};
// 重置方法
const resetEstend = () => {
	emit("reset");
	reset();
	clearSelection();
};
// 搜索方法
const searchEstend = () => {
	emit("search");
	search();
	clearSelection();
};

// 暴露给父组件的参数和方法(外部需要什么，都可以从这里暴露出去)
defineExpose({
	element: tableRef,
	tableData,
	searchParam,
	pageable,
	getTableList,
	clearSelection,
	enumMap,
	isSelected,
	selectedList,
	selectedListIds,
	toggleRowExpansion,
	reset,
	resetEstend,
	searchEstend
});
</script>
<style lang="scss" scoped>
.table-dense.card {
	padding: 0;
}
.card.table {
	:deep(.is-guttered) {
		padding-right: 0 !important;
	}
	.el-card {
		position: relative;
		.checked-box {
			position: absolute;
			top: -22px;
			right: -22px;
			display: none;
			width: 42px;
			height: 42px;
			background: var(--el-color-primary);
			opacity: 0.8;
			transform: rotate(-45deg);
		}
	}
	.card-item-active,
	.el-card:hover {
		.checked-box {
			z-index: 9;
			display: block;
			cursor: pointer;
		}
	}
	.card-item-active {
		.el-card {
			position: relative;
			box-sizing: border-box;
			border: 1px solid var(--el-color-primary);
		}
		.checked-box {
			&::after {
				position: absolute;
				bottom: 15px;
				left: 2px;
				z-index: 9;
				font-family: iconfont !important;
				font-size: 12px;
				font-style: normal;
				color: #ffffff;
				content: "\e879";
				transform: rotate(45deg);
			}
		}
	}
}
.header-button-ri {
	.el-button > span > i.iconfont {
		padding-right: 0;
		font-size: 14px;
	}
}
:deep(.el-table.is-scrolling-none th.el-table-fixed-column--left),
:deep(th.el-table-fixed-column--left.is-last-column.el-table__cell) {
	background: var(--el-fill-color-light);
}
:deep(.el-table.is-scrolling-none th.el-table-fixed-column--right) {
	background: var(--el-fill-color-light);
}
:deep(.el-scrollbar__view) {
	min-height: 230px;
}
:deep(.el-table__empty-text) {
	width: auto;
}
s.pro-table-empty {
	min-width: 300px;
}
</style>
